<!-- +----------------------------------------------------------------------
| 麦沃德科技赋能开发者，助力中小企业发展
+----------------------------------------------------------------------
| Copyright (c) 2017～2024  www.wdadmin.cn    All rights reserved.
+----------------------------------------------------------------------
| 沃德代驾系统并不是自由软件，不加密，并不代表开源，未经许可不可自由转售和商用
+----------------------------------------------------------------------
| Author: MY WORLD Team <bd@maiwd.cn>   www.wdadmin.cn
+----------------------------------------------------------------------
| 发消息 开发者: 麦沃德科技-半夏
+---------------------------------------------------------------------- -->

<template>
	<view class="container" :style="{ '--theme-color': themeColor }" v-if="loadEnd">
		<!-- 聊天区 -->
		<view class="container-main">
			<scroll-view style="height: 100%;" scroll-y refresher-background="transparent" @refresherrefresh="refresherrefresh" :refresher-enabled="hasMore" :refresher-triggered="scrollView.refresherTriggered" :scroll-into-view="scrollView.intoView">
				<view class="main-list">
					<view class="list-item" v-for="(item, index) in chatList" :key="index" :id="`item-${item.id}`">
						<view class="item-time" v-if="index == 0 || item.createtime != chatList[index - 1].createtime">{{ item.createtime }}</view>
						<view class="item-record flex align-items-center" :class="{ 'record-right': item.sender == 'user' }">
							<image class="record-avatar" :src="item.avatar" mode="aspectFill" v-if="item.sender == 'driver'"></image>
							<view class="record-content" v-html="formatMessage(item.message)"></view>
							<image class="record-avatar" :src="item.avatar" mode="aspectFill" v-if="item.sender == 'user'"></image>
						</view>
					</view>
				</view>
			</scroll-view>
		</view>
		<!-- 输入区 -->
		<view class="container-footer" v-if="sendEnd == 2">
			<view class="footer-column">
				<view class="column-title flex flex-between-center" @click="handleExpand()">
					<view class="title">常用语</view>
					<view class="btn" :style="{ transform: isExpand ? 'rotate(0)' : 'rotate(180deg)' }">
						<image src="/static/down_arrow.png" mode="aspectFit"></image>
					</view>
				</view>
				<scroll-view class="column-list" scroll-y v-if="isExpand">
					<view class="list-item flex" v-for="(item, index) in commonPhrases" :key="index">
						<view class="item-box" @click="sendCommonPhrases(item)">{{ item }}</view>
					</view>
				</scroll-view>
			</view>
			<view class="footer-form flex align-items-end">
				<view class="form-input flex-item">
					<textarea class="textarea" :cursor-spacing="16" :adjust-position="false" :auto-height="parseInt(inputLineCount) < 6" :maxlength="-1" v-model="inputContent" type="text" placeholder="请输入内容" @linechange="onLineChange" @keyboardheightchange="keyboardHeightChange"></textarea>
				</view>
				<view class="form-btn" @click="handleSend()">发送</view>
			</view>
			<view class="safe-padding"></view>
		</view>
		<!-- 行程结束 -->
		<view class="container-tips" v-else>
			<view class="tips-text">行程已结束，不能继续发消息</view>
			<view class="safe-padding"></view>
		</view>
		<!-- 底部键盘占空 -->
		<view class="container-blank" :style="{ height: keyboardHeight + 'px' }"></view>
	</view>
</template>

<script>
	import { mapState } from "vuex"
	import WebsocketUtil from '@/common/socket.js';
	export default {
		data() {
			return {
				// 加载完成
				loadEnd: false,
				// 键盘占空高度
				keyboardHeight: 0,
				// 订单id
				orderId: null,
				// 聊天列表
				chatList: [],
				// 滚动容器
				scrollView: {
					refresherTriggered: false,
					intoView: "",
				},
				// 输入框行数
				inputLineCount: 1,
				// 分类查询参数
				page: 1,
				limit: 20,
				hasMore: false,
				// 是否绑定成功
				isBind: false,
				// 通信是否已结束
				sendEnd: 2,
				// 常用语
				commonPhrases: [],
				// 输入内容
				inputContent: "",
				// 是否展开常用语
				isExpand: false,
				// WebSocket 实例
				websocket: null,
				// 是否停止监听
				isStopMessage: false,
			}
		},
		computed: {
			...mapState({
				themeColor: state => state.app.themeColor,
				websocketPort: state => state.app.websocketPort,
				domain: state => state.app.domain,
			})
		},
		onLoad(option) {
			this.orderId = option.id
			this.getCommonPhrases()
			this.$nextTick(() => {
				uni.setNavigationBarTitle({
					title: option.name
				})
			})
			this.initWebSocket()
			uni.showLoading({
				title: "加载中"
			})
			this.getChatList(() => {
				uni.hideLoading()
				this.loadEnd = true
			})
		},
		onShow() {
			if (this.loadEnd) {
				uni.pageScrollTo({
					scrollTop: 0,
					duration: 0
				});
				this.page = 1
				this.initWebSocket()
				this.getChatList()
			}
		},
		onHide() {
			if (this.websocket) this.websocket.destroy();
		},
		onUnload() {
			if (this.websocket) this.websocket.destroy();
		},
		methods: {
			// 获取聊天列表
			getChatList(fn) {
				this.$util.request("main.message.details", {
					page: this.page,
					limit: this.limit,
					order_id: this.orderId,
					type: 1,
				}).then(res => {
					if (fn) fn()
					if (res.code == 1) {
						this.sendEnd = res.data.end
						let list = res.data.data
						this.hasMore = this.page < res.data.total / this.limit ? true : false
						this.chatList = this.page == 1 ? list : [...list, ...this.chatList];
						this.scrollView.refresherTriggered = false;
						if (list.length == 0) return
						if (this.page == 1) {
							const selector = `item-${list[list.length - 1].id}`;
							this.$nextTick(() => {
								this.scrollView.intoView = selector;
							})
						} else {
							const selector = `item-${list[list.length - 1].id}`;
							this.$nextTick(() => {
								this.scrollView.intoView = selector;
							})
						}
					} else {
						uni.showToast({
							title: res.msg,
							icon: 'none'
						})
					}
				}).catch(error => {
					if (fn) fn()
					console.error('获取聊天列表 ', error)
				})
			},
			// 格式化消息内容
			formatMessage(msg) {
				if (!msg) return ''
				let html = msg.replace(/&/g, '&amp;')
				html = html.replace(/ /g, '&nbsp;')
				html = html.replace(/\r\n|\n|\r/g, '<br/>')
				return html
			},
			// 初始化 WebSocket 连接
			initWebSocket() {
				if (!this.websocketPort) {
					setTimeout(() => {
						this.initWebSocket()
					}, 200);
					return
				}
				this.websocket = new WebsocketUtil('wss://' + this.websocketPort, 5);
				// 可以注册消息回调
				this.websocket.onMessage(res => {
					if (res.type == "init") {
						this.bindChat(res.client_id)
					} else if (res.type == "onMessage") {
						const data = {
							id: new Date().getTime(),
							avatar: this.domain + res.avatar,
							createtime: res.createtime,
							message: res.message,
							sender: res.sender,
						}
						this.chatList.push(data)
						this.$forceUpdate()
						this.$nextTick(() => {
							this.scrollView.intoView = `item-${data.id}`;
						})
						// 清除未读状态
						this.$util.request("main.message.details", {
							page: 1,
							limit: 1,
							order_id: this.orderId,
							type: 1,
						})
					} else if (res.type == "send_fail") {
						uni.showToast({
							title: res.message,
							icon: "none",
							duration: 2000
						})
					} else if (res.type == "error" && !this.isStopMessage) {
						this.isStopMessage = true
						uni.showModal({
							title: "系统提示",
							content: "连接失败，请返回重试",
							confirmText: "返回",
							confirmColor: this.themeColor,
							showCancel: false,
							success: () => {
								this.isStopMessage = false
								if (getCurrentPages().length == 1) {
									this.$util.toPage({
										mode: 3,
										path: "/pages/passenger/message/index"
									})
								} else {
									uni.navigateBack()
								}
							},
						})
					}
				});
			},
			// 绑定
			bindChat(id) {
				this.$util.request("main.socket.bindChat", {
					client_id: id,
					order_id: this.orderId
				}).then(res => {
					if (res.code == 1) {
						this.isBind = true
					} else {
						this.isBind = false
						this.bindChat(id)
					}
				}).catch(error => {
					console.error('绑定 ', error)
				})
			},
			// 输入框行数改变
			onLineChange(e) {
				if (this.inputLineCount != e.detail.lineCount) {
					const list = this.chatList
					if (list.length) {
						const selector = `item-${list[list.length - 1].id}`;
						this.scrollView.intoView = ""
						this.$nextTick(() => {
							this.scrollView.intoView = selector;
						})
					}
				}
				this.inputLineCount = e.detail.lineCount
			},
			// 键盘高度改变
			keyboardHeightChange(e) {
				this.keyboardHeight = e.detail.height
				const list = this.chatList
				if (list.length) {
					const selector = `item-${list[list.length - 1].id}`;
					this.scrollView.intoView = ""
					this.$nextTick(() => {
						this.scrollView.intoView = selector;
					})
				}
			},
			// 获取常用语
			getCommonPhrases() {
				this.$util.request("main.message.phrases", {
					type: 1,
				}).then(res => {
					if (res.code == 1) {
						this.commonPhrases = res.data
					} else {
						uni.showToast({
							title: res.msg,
							icon: 'none'
						})
					}
				}).catch(error => {
					console.error('获取常用语 ', error)
				})
			},
			// 展开常用语
			handleExpand() {
				this.isExpand = !this.isExpand
				if (this.isExpand) {
					const list = this.chatList
					if (list.length) {
						const selector = `item-${list[list.length - 1].id}`;
						this.scrollView.intoView = ""
						this.$nextTick(() => {
							this.scrollView.intoView = selector;
						})
					}
				}
			},
			// 发送常用语
			sendCommonPhrases(msg) {
				let data = {
					order_id: this.orderId,
					sender: "user",
					message: msg,
					token: uni.getStorageSync("token"),
				}
				this.websocket.send(JSON.stringify(data))
				this.isExpand = false
			},
			// 发送聊天
			handleSend() {
				if (!this.inputContent || /^\s*$/.test(this.inputContent)) {
					uni.showToast({
						icon: "none",
						title: "请输入内容"
					})
					return
				}
				let data = {
					order_id: this.orderId,
					sender: "user",
					message: this.inputContent,
					token: uni.getStorageSync("token"),
				}
				this.websocket.send(JSON.stringify(data))
				this.inputContent = ""
				this.inputLineCount = 1
			},
			// 下拉刷新
			refresherrefresh() {
				if (this.hasMore) {
					this.page++
					this.getChatList()
				}
				this.scrollView.refresherTriggered = true;
			},
		}
	}
</script>

<style lang="scss">
	page {
		padding-bottom: 0;
	}

	.container {
		height: 100vh;
		display: flex;
		flex-direction: column;

		.container-main {
			flex: 1;
			overflow: hidden;

			.main-list {
				padding: 44rpx 32rpx 32rpx;

				.list-item {
					margin-top: 64rpx;

					&:first-child {
						margin-top: 0;
					}

					.item-time {
						color: #8D929C;
						font-size: 24rpx;
						line-height: 34rpx;
						padding: 8rpx 16rpx;
						text-align: center;
						margin-bottom: 28rpx;
					}

					.item-record {
						padding-right: 92rpx;

						.record-avatar {
							width: 64rpx;
							height: 64rpx;
							border-radius: 50%;
							min-width: 64rpx;
						}

						.record-content {
							margin-left: 28rpx;
							padding: 24rpx 32rpx;
							color: #242629;
							font-size: 24rpx;
							font-weight: 600;
							line-height: 34rpx;
							word-break: break-all;
							border-radius: 16rpx;
							background: #FFF;
							position: relative;

							&::before {
								content: "";
								display: block;
								position: absolute;
								top: 50%;
								left: -14rpx;
								transform: translateY(-50%);
								width: 0;
								height: 0;
								border-top: transparent solid 14rpx;
								border-right: #FFF solid 18rpx;
								border-bottom: transparent solid 14rpx;
							}
						}

						&.record-right {
							padding-left: 92rpx;
							padding-right: 0;
							justify-content: flex-end;

							.record-content {
								margin-left: 0;
								margin-right: 28rpx;

								&::before {
									border-left: #FFF solid 18rpx;
									border-right: none;
									left: auto;
									right: -14rpx;
								}
							}
						}
					}
				}
			}
		}

		.container-footer {
			border-radius: 32rpx 32rpx 0 0;
			background: #FFF;
			padding: 48rpx 32rpx 16rpx;
			margin-top: 16rpx;

			.footer-column {
				.column-title {
					.title {
						color: #242629;
						font-size: 32rpx;
						font-weight: 600;
						line-height: 44rpx;
					}

					.btn {
						width: 24rpx;
						height: 24rpx;
						transform: rotate(180deg);
						padding: 10rpx;
						box-sizing: content-box;
					}
				}

				.column-list {
					padding-bottom: 32rpx;
					max-height: 35vh;
					margin-top: 32rpx;

					.list-item {
						margin-top: 32rpx;

						&:first-child {
							margin-top: 0;
						}

						.item-box {
							color: #242629;
							font-size: 28rpx;
							font-weight: 600;
							line-height: 40rpx;
							word-break: break-all;
							padding: 14rpx 46rpx;
							border-radius: 24rpx;
							border: 1px solid #242629;
							background: #FFF;
						}
					}
				}
			}

			.footer-form {
				margin-top: 32rpx;
				border-radius: 40rpx;
				background: #F7F7F7;
				overflow: hidden;

				.form-input {
					padding: 20rpx 32rpx;

					.textarea {
						color: #8D929C;
						font-size: 28rpx;
						line-height: 40rpx;
						width: 100%;
						height: 240rpx;
					}
				}

				.form-btn {
					color: #FFF;
					text-align: center;
					font-size: 28rpx;
					font-weight: 600;
					line-height: 40rpx;
					padding: 20rpx;
					min-width: 188rpx;
					border-radius: 20px;
					background: var(--theme-color);
				}
			}
		}

		.container-tips {
			border-radius: 32rpx 32rpx 0 0;
			background: #FFF;
			padding: 32rpx;
			margin-top: 16rpx;

			.tips-text {
				text-align: center;
				color: #8D929C;
				font-size: 28rpx;
				line-height: 40rpx;
			}
		}

		.container-blank {
			width: 100%;
		}
	}
</style>